/**
 * @createTime: 2019/1/29 0029
 * @author: HingLo
 * @description: ehcache 3.x 使用
 */
package cn.hinglo.ehcahe.plugin;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.servlet.http.HttpServletRequest;

import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
import com.jfinal.core.Controller;
import com.jfinal.plugin.ehcache.CacheName;
import com.jfinal.plugin.ehcache.RenderInfo;
import com.jfinal.render.Render;

/**
 * CacheInterceptor.
 */
public class CacheInterceptor implements Interceptor {

    private static final String renderKey = "_renderKey";
    private static ConcurrentHashMap<String, ReentrantLock> lockMap = new ConcurrentHashMap<String, ReentrantLock>(512);

    private ReentrantLock getLock(String key) {
        ReentrantLock lock = lockMap.get(key);
        if (lock != null) {
            return lock;
        }
        lock = new ReentrantLock();
        ReentrantLock previousLock = lockMap.putIfAbsent(key, lock);
        return previousLock == null ? lock : previousLock;
    }

    @Override
    final public void intercept(Invocation inv) {
        Controller controller = inv.getController();
        String cacheName = buildCacheName(inv, controller);
        String cacheKey = buildCacheKey(inv, controller);
        Map<String, Object> cacheData = CacheKit.get(cacheName, cacheKey);
        if (cacheData == null) {
            Lock lock = getLock(cacheName);
            // 开锁，并发的时候，如果不开锁，就有大量的请求请求的真正的controller中，导致缓存雪崩
            lock.lock();
            try {
                cacheData = CacheKit.get(cacheName, cacheKey);
                if (cacheData == null) {
                    inv.invoke();
                    // 缓存相应的结果
                    cacheAction(cacheName, cacheKey, controller);
                    return;
                }
            } finally {
                lock.unlock();
            }
        }

        useCacheDataAndRender(cacheData, controller);
    }

    /***
     *  构建缓存的名称
     * @param inv inv
     * @param controller  controller
     * @return 返回缓存的名称
     */
    private String buildCacheName(Invocation inv, Controller controller) {
        CacheName cacheName = inv.getMethod().getAnnotation(CacheName.class);
        if (cacheName != null) {
            return cacheName.value();
        }
        cacheName = controller.getClass().getAnnotation(CacheName.class);
        return (cacheName != null) ? cacheName.value() : inv.getActionKey();
    }

    /**
     * 构建缓存的key
     *
     * @param inv        inv
     * @param controller controller
     * @return 返回key
     */
    private String buildCacheKey(Invocation inv, Controller controller) {
        StringBuilder sb = new StringBuilder(inv.getActionKey());
        String urlPara = controller.getPara();
        if (urlPara != null) {
            sb.append('/').append(urlPara);
        }
        String queryString = controller.getRequest().getQueryString();
        if (queryString != null) {
            sb.append('?').append(queryString);
        }
        return sb.toString();
    }

    /**
     * 通过继承 CacheInterceptor 并覆盖此方法支持更多类型的 Render
     */
    private RenderInfo createRenderInfo(Render render) {
        return new RenderInfo(render);
    }

    private void cacheAction(String cacheName, String cacheKey, Controller controller) {
        HttpServletRequest request = controller.getRequest();
        Map<String, Object> cacheData = new HashMap<>();
        for (Enumeration<String> names = request.getAttributeNames(); names.hasMoreElements(); ) {
            String name = names.nextElement();
            cacheData.put(name, request.getAttribute(name));
        }

        Render render = controller.getRender();
        if (render != null) {
            // cache RenderInfo
            cacheData.put(renderKey, createRenderInfo(render));
        }
        CacheKit.put(cacheName, cacheKey, cacheData);
    }

    private void useCacheDataAndRender(Map<String, Object> cacheData, Controller controller) {
        HttpServletRequest request = controller.getRequest();
        Set<Entry<String, Object>> set = cacheData.entrySet();
        for (Entry<String, Object> entry : set) {
            request.setAttribute(entry.getKey(), entry.getValue());
        }
        request.removeAttribute(renderKey);

        RenderInfo renderInfo = (RenderInfo) cacheData.get(renderKey);
        if (renderInfo != null) {
            // 设置返回的数据
            controller.render(renderInfo.createRender());
        }
    }
}





